回顧一下之前的進度,我們還在整個異步流程中的
步驟 6
。今天會跟大家介紹如何使用@effect/schema
的Class
,以便更好的做未知型別的解析。
使用 Struct 加上 Data 的語法
const adminSchema = S.data(
S.struct({
_tag: S.literal('Administrator'),
name: S.string,
birth: S.Date,
})
)
interface Adminstrator extends S.Schema.To<typeof adminSchema>
使用 Class
的語法建立 Administrator
類別
class Administrator extends S.Class<Administrator>()({
_tag: S.literal('Administrator'),
name: S.string,
birth: S.Date
}) {}
我們可以把 Administrator
當作 type
或 interface
使用,相當於一般 schema
的 To
型別
const getBirth = (admin:Administrator)=>admin.birth //return type is Date
也可以把它當 schema
來用
const admin = pipe(
'xxxxx',
S.parseEither(Administrator)
)
// Either<ParseError, Administrator>
在兩種做法都能達到相同功能的前提下,私心推薦使用語法更簡潔的 Class
不過使用 class
在方便的同時也有一個隱憂,就是 class
隱藏著很多內部狀態。貿然的改變它,很容易造成副作用
,因此我們必須先認識 immutability
(不變性),才能避免誤用。
不變性是一個非常重要且優秀的特性。
仔細觀察我們之前的範例,會發現我們都只使用 const
而不使用 let
,其實就是基於不變性的考量。我們應該盡可能的不要去變更變數,這是因為當我們對可變的物件進行操作時,有可能影響其他使用這個物件的程式,導致產生副作用
。
再次提醒 FP 真的很討厭副作用
請看下面這個例子
Array.prototype.push("╮(╯_╰)╭")
const myArray = []
請問 myArray[0]
會是什麼呢? 直覺上我們會覺得是 undefined
,但實際上它是 "╮(╯_╰)╭"
所以更好的做法是每次要對一個變數進行計算,就重新產生新的。這樣說可能有點抽象,請看以下範例
const a = [1, 2, 3]
const b = a.push(4)
// a: [1, 2, 3, 4]
// b: [1, 2, 3, 4]
// 雖然我們只是想建立 b,可是 a 也被改變了,這很危險 !
const a = [1, 2, 3]
const b = [...a, 4]
// a: [1, 2, 3]
// b: [1, 2, 3, 4]
類似 effect 這樣完整的 FP 函式庫,也會提供好用的工具
import { ReadonlyArray as RA } from 'effect'
const a = [1, 2, 3]
const b = RA.append(4)
// a: [1, 2, 3]
// b: [1, 2, 3, 4]
// 題外話: 這時候 b 的型別是 [number, ...[number]] ,這樣我們就可以確定它不是空的 !
以 class 來說,我們可以使用 getter
class Administrator extends S.Class<Administrator>()({
_tag: S.literal('Administrator'),
name: S.string,
birth: S.Date
}) {
get upperName() {
return this.name.toUpperCase
}
}
也可以使用不涉及 this 的變更的 method
class Administrator extends S.Class<Administrator>()({
_tag: S.literal('Administrator'),
name: S.string,
birth: S.Date
}) {
greeting() {
return this.name.toUpperCase
}
}
只要正確使用,Class 也能很 FP !